# coding:utf-8

import csv
import os
from typing import Dict
from typing import Iterator
from typing import List
from typing import Optional
from typing import Sequence

from tabulate import tabulate


class poll:

    class vid:

        def __init__(self, building: int, floor: int, room: int):
            assert isinstance(building, int)
            assert building >= 1 and building <= 12
            assert isinstance(floor, int) and floor >= 1 and floor <= 33
            assert isinstance(room, int) and room >= 1 and room <= 8
            self.__building = building
            self.__floor = floor
            self.__room = room

        def _str__(self):
            return self.id

        @property
        def id(self) -> str:
            return f"{self.building}-{self.floor}{self.room:02}"

        @property
        def building(self) -> int:
            return self.__building

        @property
        def floor(self) -> int:
            return self.__floor

        @property
        def room(self) -> int:
            return self.__room

    class vtime:

        def __init__(self, start: str, finish: str):
            assert isinstance(start, str)
            assert isinstance(finish, str)
            self.__start = start
            self.__finish = finish

        @property
        def start(self) -> str:
            return self.__start

        @property
        def finish(self) -> str:
            return self.__finish

    class vitem:
        MAP = {
            1: "同意拆除",
            2: "不同意拆除",
            3: "弃权",
        }

        def __init__(self, id, no: int, time, select: int, other: str):
            assert isinstance(no, int)
            assert isinstance(id, poll.vid)
            assert isinstance(time, poll.vtime)
            assert isinstance(select, int)
            assert isinstance(other, str)
            self.__id = id
            self.__no = no
            self.__time = time
            self.__select = select
            self.__other = other

        def _str__(self):
            return " ".join(self.datas)

        @classmethod
        def title(cls) -> Sequence[str]:
            return ("序号", "房号", "时间", "投票", "签名", "其他意见")

        @property
        def datas(self) -> List[str]:
            return [
                str(self.no), self.id_str, self.time, self.select_str,
                self.signature, self.other
            ]

        @property
        def no(self) -> int:
            return self.__no

        @property
        def id(self):
            return self.__id

        @property
        def id_str(self) -> str:
            return self.__id.id

        @property
        def time(self) -> str:
            return f"{self.__time.start}"

        @property
        def select(self) -> int:
            return self.__select

        @property
        def select_str(self) -> str:
            return poll.vitem.MAP[self.select]

        @property
        def other(self) -> str:
            return self.__other

        @property
        def signature(self) -> str:
            path = os.path.join("..", "signature", f"{self.no}.png")
            return f'![签名]({path})'

    class invalid:

        def __init__(self, item, message: str):
            assert isinstance(item, poll.vitem)
            assert isinstance(message, str)
            self.__item = item
            self.__message = message

        def _str__(self):
            return " ".join(self.datas)

        @classmethod
        def title(cls) -> Sequence[str]:
            return ("序号", "房号", "时间", "投票", "签名", "其他意见", "无效原因")

        @property
        def datas(self) -> List[str]:
            datas = self.item.datas
            datas.append(self.message)
            return datas

        @property
        def item(self):
            return self.__item

        @property
        def message(self) -> str:
            return self.__message

    class counter:

        def __init__(self, building: int):
            assert isinstance(building, int)
            self.__building = building
            self.__ballot: Dict[str, poll.vitem] = {}
            self.__invalid_ballot: List[poll.invalid] = []

        def __iter__(self):
            return self.ballot

        @property
        def abstract(self) -> Dict[str, int]:
            abstract = {
                "有效投票": len(self.__ballot),
                "无效投票": len(self.__invalid_ballot),
            }
            for item in self.ballot:
                if item.select_str not in abstract:
                    abstract[item.select_str] = 1
                else:
                    abstract[item.select_str] += 1
            return abstract

        @property
        def building(self) -> int:
            return self.__building

        @property
        def ballot(self):
            return iter(self.__ballot.values())

        @property
        def invalid_ballot(self):
            return iter(self.__invalid_ballot)

        def add(self, item) -> bool:
            assert isinstance(item, poll.vitem)
            assert item.id.building == self.building

            # check room
            if item.id.room > 4 and item.id.building not in [3, 11]:
                self.__invalid_ballot.append(poll.invalid(item, "无效房号"))
                return False

            if item.id_str not in self.__ballot:
                self.__ballot[item.id_str] = item
                return True

            if self.__ballot[item.id_str].select != item.select:
                self.__invalid_ballot.append(
                    poll.invalid(self.__ballot.pop(item.id_str),
                                 f"与序号{item.no}房号相同，但投票不一致"))
            self.__invalid_ballot.append(poll.invalid(item, "重复投票"))
            return False

        def dump(self) -> str:

            def title():
                if self.building not in [3, 11]:
                    return ("", "01", "02", "03", "04")
                return ("", "01", "02", "03", "04", "05", "06", "07", "08")

            def table():
                table = []
                for i in range(33):
                    line = [str(i + 1)]
                    for _ in range(len(title()) - 1):
                        line.append("")
                    table.append(line)
                return table

            datas = table()
            for v in self.ballot:
                datas[v.id.floor - 1][v.id.room] = {
                    1: "√",
                    2: "×",
                    3: "o"
                }[v.select]

            return tabulate(datas, headers=title(), tablefmt="pipe")

    def __init__(self):
        self.__buildings: Dict[int, poll.counter] = {
            1: self.counter(1),
            2: self.counter(2),
            3: self.counter(3),
            4: self.counter(4),
            5: self.counter(5),
            6: self.counter(6),
            7: self.counter(7),
            8: self.counter(8),
            9: self.counter(9),
            10: self.counter(10),
            11: self.counter(11),
            12: self.counter(12),
        }

    def __iter__(self):
        return self.ballot

    @property
    def buildings(self) -> Dict[int, counter]:
        return self.__buildings

    @property
    def ballot(self) -> Iterator[vitem]:
        ballot: List[poll.vitem] = []
        for _, building in self.buildings.items():
            ballot.extend(building.ballot)
        ballot.sort(key=lambda i: i.no)
        return iter(ballot)

    @property
    def invalid_ballot(self) -> Iterator[invalid]:
        invalid_ballot: List[poll.invalid] = []
        for _, building in self.buildings.items():
            invalid_ballot.extend(building.invalid_ballot)
        invalid_ballot.sort(key=lambda i: i.item.no)
        return iter(invalid_ballot)

    def add(self, item: vitem) -> bool:
        assert isinstance(item, self.vitem)
        assert item.id.building in self.buildings
        return self.buildings[item.id.building].add(item)


def main(argv: Optional[Sequence[str]] = None) -> int:

    def read(path: str):
        with open(path, "r", newline=None) as fd:
            ballot = [v for v in csv.DictReader(fd)]
            return ballot

    def write(voting: poll):
        assert isinstance(voting, poll)

        with open(os.path.join("buildings", "stat.md"), "w") as fd:
            print("投票统计\n========\n", file=fd)

            print("有效票\n------\n", file=fd)
            print(tabulate([item.datas for item in voting.ballot],
                           headers=poll.vitem.title(),
                           tablefmt="pipe"),
                  file=fd)
            fd.write("\n")

            print("无效票\n------\n", file=fd)
            print(tabulate([item.datas for item in voting.invalid_ballot],
                           headers=poll.invalid.title(),
                           tablefmt="pipe"),
                  file=fd)
            fd.write("\n")

        for building, counter in voting.buildings.items():
            with open(os.path.join("buildings", f"{building}.md"), "w") as fd:
                print("投票统计\n========\n", file=fd)
                for key, value in counter.abstract.items():
                    print(f"{key}: {value}", file=fd)
                fd.write("\n")

                print(counter.dump(), file=fd)
                fd.write("\n")

                print('"√": 同意 "×": 不同意 "o": 弃权', file=fd)
                fd.write("\n")

                print("有效票\n------\n", file=fd)
                print(tabulate([item.datas for item in counter.ballot],
                               headers=poll.vitem.title(),
                               tablefmt="pipe"),
                      file=fd)
                fd.write("\n")

                print("无效票\n------\n", file=fd)
                print(tabulate([item.datas for item in counter.invalid_ballot],
                               headers=poll.invalid.title(),
                               tablefmt="pipe"),
                      file=fd)
                fd.write("\n")

    ballot = read(os.path.join("datas.csv"))
    key_no = list(ballot[0].keys())[0]
    ballot.sort(key=lambda i: int(i[key_no]))

    voting = poll()
    for v in ballot:
        no = int(v[key_no])
        start = v["开始答题时间"]
        finish = v["结束答题时间"]
        time = poll.vtime(start, finish)

        building = int(v["1.请选择楼栋"])
        floor = int(v["2.请选择楼层"])
        room = int(v["3.请选择房号"])
        id = poll.vid(building, floor, room)

        select = int(v["4.是否同意拆除电梯广告"])
        other = v["5.关于电梯广告的其他意见"]
        item = poll.vitem(id, no, time, select, other)
        voting.add(item)

    write(voting)
    return 0


if __name__ == "__main__":
    main()
